package com.wzh.project.utils.json;


import java.io.IOException;
import java.lang.reflect.Type;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.css.sword.kernel.base.exception.SwordBaseCheckedException;
import com.css.sword.kernel.platform.component.exception.SwordExceptionParser;
import com.css.sword.kernel.utils.SwordDateUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.DateDeserializers.DateDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.DateSerializer;
import com.fasterxml.jackson.databind.type.CollectionLikeType;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;

public class SwordJsonUtils {
	
	private static final Map<String, ObjectMapper> dateFormatMapperCaches = new ConcurrentHashMap<>(0);
	private static final String defaultDateFormat = "yyyy-MM-dd HH:mm:ss";

	/**
	 * 内部方法获取指定日期格式解析的Gson对象
	 * @param dateFormat
	 * @return
	 */
	private static ObjectMapper getMapperByDateFormat(String dateFormat) {
		ObjectMapper g = dateFormatMapperCaches.get(dateFormat);
		if (g == null) {
			synchronized (dateFormatMapperCaches) {
				g = dateFormatMapperCaches.get(dateFormat);
				if (g == null) {
					g = new ObjectMapper();
					g.setDateFormat(new MySimpleDateFormat(dateFormat));
					g.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
					g.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
					
					// 正常情况不需要此类,但是jackson默认sqldate是不带时分秒的，而有些时候sqldate我们也需要保留时分秒
					SimpleModule module = new SimpleModule("SwordCustomDateJsonModule", PackageVersion.VERSION);
					module.addSerializer(Date.class, new CustomDateSerializer(dateFormat));
					module.addDeserializer(Date.class, new CustomDateDeserializer(dateFormat));
					g.registerModule(module);
					
					dateFormatMapperCaches.put(dateFormat, g);
				}
			}
		}
		return g;
	}
	
	/**
	 * 对象转换到json，如果对象中包含日期类型，那么日期格式按照默认日期格式 yyyy-MM-dd HH:mm:ss
	 * @param obj
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static String toJson(Object obj) throws SwordBaseCheckedException {
		return toJson(obj, defaultDateFormat);
	}
	/**
	 * 对象转换到json，如果对象中包含日期类型，按照参数dateFormat指定的日期格式
	 * @param obj
	 * @param dateFormat 日期格式
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static String toJson(Object obj, String dateFormat) throws SwordBaseCheckedException {
		ObjectMapper om = getMapperByDateFormat(dateFormat);
		try {
			return om.writeValueAsString(obj);
		} catch (JsonProcessingException e) {
			throw SwordExceptionParser.processException("对象到json时出错", e);
		}
	}
	/**
	 * json转换到目标对象,如果目标对象对应的字段有日期类型，将按照默认日期格式解析yyyy-MM-dd HH:mm:ss 
	 * @param json 字符串
	 * @param cls 目标类
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <T> T toObject(String json, Class<T> cls) throws SwordBaseCheckedException {
		return toObject(json, cls, defaultDateFormat);
	}
	/**
	 * json转换到目标对象,如果目标对象对应的字段有日期类型，将按照指定的日期格式解析 
	 * @param json 字符串
	 * @param cls 目标类
	 * @param dateFormat 日期格式
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <T> T toObject(String json, Class<T> cls, String dateFormat) throws SwordBaseCheckedException {
		ObjectMapper om = getMapperByDateFormat(dateFormat);
		try {
			return om.readValue(json, cls);
		} catch (IOException e) {
			throw SwordExceptionParser.processException("json2object时出错", e);
		}
	}
	
	/**
	 * json转换到目标对象,如果目标对象是日期类型，将按照默认日期格式解析yyyy-MM-dd HH:mm:ss
	 * @param json 字符串
	 * @param type 类型
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <T> T toObject(String json, Type type) throws SwordBaseCheckedException {
		return toObject(json, type, defaultDateFormat);
	}
	/**
	 * json转换到目标对象,如果目标对象是日期类型，将按照指定的日期格式解析 
	 * @param json 字符串
	 * @param type 类型
	 * @param dateFormat 日期格式
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <T> T toObject(String json, Type type, String dateFormat) throws SwordBaseCheckedException {
		ObjectMapper om = getMapperByDateFormat(dateFormat);
		JavaType javaType = om.getTypeFactory().constructType(type);
		try {
			return om.readValue(json, javaType);
		} catch (IOException e) {
			throw SwordExceptionParser.processException("json2object时出错", e);
		}
	}
	
	/**
	 * json转换为List,List中对象为cls,如果目标对象的对应字段是日期类型，将按照默认日期格式解析yyyy-MM-dd HH:mm:ss 
	 * @param json 字符串
	 * @param cls list中对象类型
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <T> List<T> toList(String json, Class<T> cls) throws SwordBaseCheckedException {
		return toList(json, cls, defaultDateFormat);
	}
	@SuppressWarnings("rawtypes")
	public static <T> List<T> toList(String json, Class<? extends List> listClass, Class<T> cls) throws SwordBaseCheckedException {
		return toList(json, listClass, cls, defaultDateFormat);
	}
	/**
	 * json转换为List,List中对象为cls,如果目标对象的对应字段是日期类型，将按照指定的日期格式解析 
	 * @param json 字符串
	 * @param cls list中对象类型
	 * @param dateFormat 日期格式
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <T> List<T> toList(String json, Class<T> cls, String dateFormat) throws SwordBaseCheckedException {
		return toList(json, ArrayList.class, cls, dateFormat);
	}
	@SuppressWarnings("rawtypes")
	public static <T> List<T> toList(String json, Class<? extends List> listClass, Class<T> cls, String dateFormat) throws SwordBaseCheckedException {
		ObjectMapper om = getMapperByDateFormat(dateFormat);
		CollectionLikeType type = om.getTypeFactory().constructCollectionLikeType(listClass, cls);
		try {
			return om.readValue(json, type);
		} catch (IOException e) {
			throw SwordExceptionParser.processException("json2List时出错", e);
		}
	}

	/**
	 * json转换为Set,Set中对象为cls,如果目标对象的对应字段是日期类型，将按照默认日期格式解析yyyy-MM-dd HH:mm:ss 
	 * @param json 字符串
	 * @param cls set中对象类型
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <T> Set<T> toSet(String json, Class<T> cls) throws SwordBaseCheckedException {
		return toSet(json, cls, defaultDateFormat);
	}
	/**
	 * json转换为Set,Set中对象为cls,如果目标对象的对应字段是日期类型，将按照指定日期格式解析 
	 * @param json 字符串
	 * @param cls set中对象类型
	 * @param dateFormat 日期格式
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <T> Set<T> toSet(String json, Class<T> cls, String dateFormat) throws SwordBaseCheckedException {
		ObjectMapper om = getMapperByDateFormat(dateFormat);
		CollectionType type = om.getTypeFactory().constructCollectionType(HashSet.class, cls);
		try {
			return om.readValue(json, type);
		} catch (IOException e) {
			throw SwordExceptionParser.processException("json2set时出错", e);
		}
	}
	/**
	 * json转换为Map,并指定key类型和value类型,如果存在日期类型，则按默认日期格式解析yyyy-MM-dd HH:mm:ss 
	 * @param json 字符串
	 * @param keyType key类型
	 * @param valueType value类型
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <K, V> Map<K, V> toMap(String json, Class<K> keyType, Class<V> valueType) throws SwordBaseCheckedException {
		return toMap(json, keyType, valueType, defaultDateFormat);
	}
	/**
	 * json转换为Map,并指定key类型和value类型,如果存在日期类型，则按指定的日期格式解析 
	 * @param json 字符串
	 * @param keyType key类型
	 * @param valueType value类型
	 * @param dateFormat 日期格式
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static <K, V> Map<K, V> toMap(String json, Class<K> keyType, Class<V> valueType, String dateFormat) throws SwordBaseCheckedException {
		ObjectMapper om = getMapperByDateFormat(dateFormat);
		MapType mapType = om.getTypeFactory().constructMapType(LinkedHashMap.class, keyType, valueType);
		try {
			return om.readValue(json, mapType);
		} catch (IOException e) {
			throw SwordExceptionParser.processException("json2map时出错", e);
		}
	}
	
	/**
	 * 将json转换为json数组
	 * @param json
	 * @return
	 * @throws SwordBaseCheckedException
	 */
	public static String[] toJsonArray(String json) throws SwordBaseCheckedException {
		try {
			ObjectMapper om = getMapperByDateFormat(defaultDateFormat);
			List<?> list = om.readValue(json, List.class);
			String[] jsonArray = new String[list.size()];
			for (int i = 0; i < list.size(); i++) {
				jsonArray[i] = om.writeValueAsString(list.get(i));
			}
			return jsonArray;
		} catch (IOException e) {
			throw SwordExceptionParser.processException("解析json到json数组时出错", e);
		}
	}
	
	/**
	 * Date类型序列化器，内部使用SwordDateUtils
	 */
	private static class CustomDateSerializer extends DateSerializer {
		private static final long serialVersionUID = 1L;
		private final String dateFormat;
		public CustomDateSerializer(String dateFormat) {
			this.dateFormat = dateFormat;
		}
		@Override
		public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException {
			gen.writeString(SwordDateUtils.toDateStrByFormat(value, dateFormat));
		}
	}
	private static class CustomDateDeserializer extends DateDeserializer {
		private static final long serialVersionUID = 1L;
		private final String dateFormat;
		public CustomDateDeserializer(String dateFormat) {
			this.dateFormat = dateFormat;
		}
		@Override
		public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
			return SwordDateUtils.parseDate(p.getText(), dateFormat);
		}
	}
	/**
	 * 自定义SimpleDateFormat格式序列化器，内部使用SwordDateUtils
	 */
	private static class MySimpleDateFormat extends SimpleDateFormat {
		private static final long serialVersionUID = 1L;
		private final String dateFormat;
		public MySimpleDateFormat(String dateFormat) {
			super(dateFormat);
			this.dateFormat = dateFormat;
		}
		@Override
		public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
			String str = SwordDateUtils.toDateStrByFormat(date, dateFormat);
			return new StringBuffer(str);
		}
		@Override
		public Date parse(String source) throws ParseException {
			return SwordDateUtils.parseDate(source, dateFormat);
		}
	}
}
